package com.wisdom.system.api.core.oauth;

import com.wisdom.system.api.core.oauth.Exception.AuthExceptionEntryPoint;
import com.wisdom.system.api.core.oauth.Exception.CustomAccessDeniedHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;

import java.util.concurrent.TimeUnit;

@Configuration
public class OAuth2ServerConfig {

    private static final String MI_RESOURCE_ID = "api";

    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.authenticationEntryPoint(new AuthExceptionEntryPoint())
                    .accessDeniedHandler(new CustomAccessDeniedHandler());
            resources.resourceId(MI_RESOURCE_ID).stateless(true);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.exceptionHandling()
                    .and()
                    .logout()
                    .logoutUrl("/oauth/logout")
                    .and()
                    .authorizeRequests()
                    //todo 授权接口配置
                    .antMatchers("/api/**").authenticated();
            http.authorizeRequests().and().cors();
        }
    }

    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

        @Autowired
        AuthenticationManager authenticationManager;

        @Autowired
        RedisConnectionFactory redisConnectionFactory;

        @Value("${oauth.token.expire}")
        private int expire;

        @Value("${oauth.token.refresh}")
        private int refresh;

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            //支持多种编码，通过密码的前缀区分编码方式
            String finalSecret = new BCryptPasswordEncoder().encode("123456");
            //配置两个客户端,一个用于password认证一个用于client认证
            clients.inMemory()
                    .withClient("client")
                    .resourceIds(MI_RESOURCE_ID)
                    .authorizedGrantTypes("password", "refresh_token")
                    .scopes("select")
                    .authorities("oauth2")
                    .secret(finalSecret)
                    //todo 客户端1重写token有效时长
                    .accessTokenValiditySeconds(expire)
                    //todo 客户端1重写refresh_token有效时长
                    .refreshTokenValiditySeconds(refresh)
                    .and()
                    .withClient("client1")
                    .resourceIds(MI_RESOURCE_ID)
                    .authorizedGrantTypes("password", "refresh_token")
                    .scopes("select")
                    .authorities("oauth2")
                    .secret(finalSecret)
                    //todo 客户端2重写token有效时长
                    .accessTokenValiditySeconds((int)TimeUnit.MINUTES.toSeconds(expire))
                    //todo 客户端2重写refresh_token有效时长
                    .refreshTokenValiditySeconds((int)TimeUnit.MINUTES.toSeconds(refresh));
        }

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            //自定义RedisTokenStore，可以根据用户浏览刷新token时限
            endpoints
                    .tokenStore(new MyRedisTokenStoreService(redisConnectionFactory,null))
                    .authenticationManager(authenticationManager)
                    .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE);

            //配置TokenService参数
            DefaultTokenServices tokenService = new DefaultTokenServices();
            tokenService.setTokenStore(endpoints.getTokenStore());
            tokenService.setSupportRefreshToken(true);
            tokenService.setClientDetailsService(endpoints.getClientDetailsService());
            tokenService.setTokenEnhancer(endpoints.getTokenEnhancer());
            tokenService.setAccessTokenValiditySeconds(expire);
            //refresh token时长大于2倍的access_token时长，当access_token过期，
            //前端发送refresh_token重新获取access_token和refresh_token，如此实现登录一直操作登录不过期
            tokenService.setRefreshTokenValiditySeconds(refresh);
            //该字段设置设置refresh token是否重复使用,true:reuse;false:no reuse.
            tokenService.setReuseRefreshToken(false);
            endpoints.tokenServices(tokenService);
        }

        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
            //允许表单认证
            oauthServer.allowFormAuthenticationForClients();
        }
    }
}
